<?php

require_once(dirname(__FILE__)."/amazone-sdk/sdk.class.php");

/*
// Amazon Dynamo DB Module
//
// Copyright © 2012 by ProgDesigner, Inc. All rights reserved.
// GNU General Public License
*/
class Storage 
{
	const ARRAY_TYPE_DELEMETER	= ':';
	const ARRAY_TYPE_UNKNOWN	= 'AU';
	const ARRAY_TYPE_KEY 		= 'AK';
	const ARRAY_TYPE_INDEX 		= 'AI';
	
	const DEBUG_MODE_NONE		= 0;
	const DEBUG_MODE_ERROR		= 2;
	const DEBUG_MODE_ALWAYS		= 3;
}

final class helper_dynamodb
{	
	private $s3 = null;
	private $dynamoDB = null;
	
	public $debug = Storage::DEBUG_MODE_NONE;
	public $error = null;
	public $prefix = "";
	
	function __construct() {
		
	}
	
	private function _response( $response, &$error=null, $debug=false ) {
		
		if ( $error != null || (is_array($error) && count($error)==0) ) {
			$error["type"] = $response->body->__type;
			$error["message"] = $response->body->message;
			$error["status"] = $response->status;
		}
		
		if ( $this->debug == Storage::DEBUG_MODE_ALWAYS || ( $error != null && $this->debug == Storage::DEBUG_MODE_ERROR ) || $debug ) {
			showArray( $response );
		}
		
		$this->error = $error;
		
		return ( $response->isOK() ) ? true : false;
	}
	
	private function response( $response, $debug=false ) {
		$error = array();
		
		if ( ! $this->_response( $response, &$error, $debug ) ) {
			return $error;
		}
		
		return null;
	}
	
	public function init($config) {
		
		CFCredentials::set($config);
		
		$sts = new AmazonSTS();
		$response = $sts->get_session_token();
		if ($response->isOK()) {
		    $credentials = array(
		        'key' => (string)$response->body->GetSessionTokenResult->Credentials->AccessKeyId,
		        'secret' => (string)$response->body->GetSessionTokenResult->Credentials->SecretAccessKey,
		        'token' => (string)$response->body->GetSessionTokenResult->Credentials->SessionToken,
		    );
		}
		else {
		    echo('Amazon Temporary credentials could not retrieved.');
		    exit;
		}
		
		$this->s3 = new AmazonS3($credentials);
		
		$this->dynamoDB = new AmazonDynamoDB();
	}
	
	// Working Table 
	public function createTable( $table_name, $hash_key_name = "Id", $hash_key_type = AmazonDynamoDB::TYPE_STRING, $read_capaciry_units = 10, $write_capacity_units = 5, $non_prefix = false ) {
		$response = $this->dynamoDB->create_table(array(
			'TableName' => $this->_getTableName( $table_name, $non_prefix ), 
		    'KeySchema' => array(
		        'HashKeyElement' => array(
		            'AttributeName' => $hash_key_name,
		            'AttributeType' => $hash_key_type
		        )
		    ),
		    'ProvisionedThroughput' => array(
		        'ReadCapacityUnits' => $read_capaciry_units,
		        'WriteCapacityUnits' => $write_capacity_units
		    )
		));
		
		return $this->response( $response );
	}
	
	public function updateTable( $table_name, $read_capaciry_units = 10, $write_capacity_units = 5, $non_prefix = false ) {
		$response = $this->dynamoDB->update_table(array(
			'TableName' => $this->_getTableName( $table_name, $non_prefix ),
			'ProvisionedThroughput' => array(
		        'ReadCapacityUnits' => $read_capaciry_units,
		        'WriteCapacityUnits' => $write_capacity_units
		    )
		));
		
		return $this->response( $response );
	}
	
	public function deleteTable( $table_name, $non_prefix = false ) {
		$response = $this->dynamoDB->delete_table(array(
			'TableName' => $this->_getTableName( $table_name, $non_prefix )
		));
		
		return $this->response( $response );
	}

	private function _getTableName( $table_name, $non_prefix = false ) {
		return ( $non_prefix ) ? $table_name : $this->prefix.$table_name;
	}
	
	public function getTableNames() {
		$tables = array();

		// Collect all table names
		do {
		    $response = $this->dynamoDB->list_tables(array(
		        'Limit' => 2, 
		        'ExclusiveStartTableName' => isset($response) ? (string) $response->body->LastEvaluatedTableName : null
		    ));
			
			$this->response($response);
		    
		    $tables = array_merge( $tables, $response->body->TableNames()->map_string() );
		}
		while ($response->body->LastEvaluatedTableName);
		
		return $tables;
	}
	
	public function getTableInfo( $table_name, $non_prefix = false ) {
		$response = $this->dynamoDB->describe_table(array(
			'TableName' => $this->_getTableName( $table_name, $non_prefix )
		));
		
		$error = $this->response( $response );
		
		if ( $error ) {
			return Array();
		}
		
		$info = $response->body;
		
		return $info->Table;
		/*
		return Array(
			'TableName' => (String)$info->TableName,
			'ItemCount' => (int)$info->ItemCount,
			'CreationDateTime' => (double)$info->CreationDateTime,
			'TableStatus' => (String)$info->TableStatus,
			'KeySchema' => array(
				'HashKeyElement' => array(
					'AttributeName' => (String)$info->KeySchema->HashKeyElement->AttributeName,
					'AttributeType' => (String)$info->KeySchema->HashKeyElement->AttributeType
				)
			),
			'ProvisionedThroughput' => array(
				'ReadCapacityUnits' => (int)$info->ProvisionedThroughput->ReadCapacityUnits,
				'WriteCapacityUnits' => (int)$info->ProvisionedThroughput->WriteCapacityUnits
			)
		);
		*/
	}
	
	// Working Item
	private function _getArray( $array ) {
		$stringArray = array();
		
		if ( count($array) > 0 ) {
			foreach( $array as $key => $value ) {
				$type = Storage::ARRAY_TYPE_UNKNOWN;
				$string = "";
				
				if ( is_numeric($key) ) {
					$type = Storage::ARRAY_TYPE_INDEX;
					$string = base64_encode(json_encode($value));
				}
				else if ( is_string($key) ) {
					$type = Storage::ARRAY_TYPE_KEY;
					$string = base64_encode(json_encode(array($key=>$value)));
				}
				
				array_push($stringArray, sprintf("%s%s%s", $type, Storage::ARRAY_TYPE_DELEMETER, $string));
			}
		}
		else {
			$stringArray = $array;
		}
		
		return $stringArray;
	}
	
	private function _getObject( $object ) {
		return json_encode( $object );
	}
	
	public function attribute( $data ) {
		$items = array();
		
		foreach( $data as $key => $value ) {
			$items[$key] = $this->_getValue($value);
		}
		
		return $items;
	}
	
	private function _getValue( $value ) {
		$type = gettype($value);
		
		switch ( $type ) {
			case "integer":
				$type = AmazonDynamoDB::TYPE_NUMBER;
				$value = (String)$value;
			break;
			
			case "array":
				$type = AmazonDynamoDB::TYPE_ARRAY_OF_STRINGS;
				$value = $this->_getArray( $value );
			break;
			
			case "object":
				$type = AmazonDynamoDB::TYPE_STRING;
				$value = $this->_getObject( $value );
			break;
			
			case "resource":
				$type = AmazonDynamoDB::TYPE_STRING;
				$value = ( ! $value ) ? "NULL" : (String)$value;
			break;
			
			case "boolean":
				$type = AmazonDynamoDB::TYPE_STRING;
				$value = ($value == true) ? 'TRUE' : 'FALSE';
			break;
			
			case "double":
			case "string":
			default:
				$type = AmazonDynamoDB::TYPE_STRING;
				$value = ( ! $value ) ? "NULL" : (String)$value;
			break;
		}
		
		return array( $type => $value );
	}
	
	private function _getHashKeyElement( $value ) {
		$type = gettype($value);
		
		switch ( $type ) {
			case "integer":
				$type = AmazonDynamoDB::TYPE_NUMBER;
				$value = (String)$value;
			break;
			
			case "string":
			default:
				$type = AmazonDynamoDB::TYPE_STRING;
				$value = (String)$value;
			break;
		}
		
		return array( $type => $value );
	}
	
	private function _arrayWithItem( $item ) {
		foreach( $item as $key => $value ) {
			
			if ( $key == AmazonDynamoDB::TYPE_ARRAY_OF_STRINGS ) {
				$temp = $value;
				$value = array();
				
				foreach( $temp as $i => $v ) {
					if ( is_string($v) && strrpos( $v, Storage::ARRAY_TYPE_DELEMETER ) ) {
						$split_value = explode(Storage::ARRAY_TYPE_DELEMETER, $v);
						
						$decode_key = $split_value[0];
						$decode_value = json_decode( base64_decode($split_value[1]));
						
						if ( is_object($decode_value) ) {
							$decode_value = object2Array($decode_value);
							$value = array_merge( $value, $decode_value );
						}
						else {
							array_push( $value, $decode_value );
						}
					}
				}
				
				$item = $value;
				continue;
			}
			else if ( $key == AmazonDynamoDB::TYPE_STRING ) {
				$item = ($value == "NULL" ) ? null : $value;
				continue;
			}
			else if ( $key == AmazonDynamoDB::TYPE_NUMBER ) {
				$item = (int)$value;
				continue;
			}
			else {
				if ( is_array( $value ) ) {
					$value = $this->_arrayWithItem($value);
				}
				
				$item[$key] = $value;
			}
		}
		
		return $item;
	}
	
	public function objectWithXML( $xml ) {
		//$xml->to_stdClass();
		//$xml->to_json();
		//$xml->to_string();
		//$xml->to_yaml();
		
		$result = $xml->to_array();
		$object = array();
		
		foreach( $result as $key => $value ) {
			
			if ( is_array( $value ) ) {
				$value = $this->_arrayWithItem( $value );
			}
			
			$object[$key] = $value;
		}
		
		return $object;
	}
	
	public function _putItem( $table_name, $item, $non_prefix = false ) {
		
		$response = $this->dynamoDB->put_item(array(
			'TableName' => $this->_getTableName( $table_name, $non_prefix ),
			'Item' => $item
		));
		
		return $this->response( $response );
	}
	
	public function putItem( $table_name, $data, $non_prefix = false ) {
		
		$item = $this->attribute( $data );
		
		$this->_putItem( $table_name, $item, $non_prefix );
	}
	
	public function send() {
		$response = $this->dynamoDB->send();
		
		return $this->response( $response );
	}
	
	public function deleteItem( $table_name, $id ) {
		$response = $this->dynamoDB->delete_item(array(
			'TableName' => $this->prefix.$table_name,
			'Key' => array(
				'HashKeyElement' => $this->_getHashKeyElement( $id )
			)
		));
		
		return $this->response( $response );
	}
	
	public function getItem( $table_name, $id, $exclude_keys = array(), $non_prefix = false ) {
		$response = $this->dynamoDB->get_item(array(
			'TableName' => $this->_getTableName( $table_name, $non_prefix ),
			'Key' => array(
				'HashKeyElement' => $this->_getHashKeyElement( $id )
			)
			
			/*
			),
			
			'ConsistentRead' => 'true',
			'AttributesToGet' => array( 'Id', $name )
			*/
		));
		
		$error = $this->response( $response );
		
		if ( $error ) {
			return null;
		}
		
		$item = $this->objectWithXML( $response->body );

		if ( isset( $item['Item'] ) ) {
			$data = $item['Item'];
			
			foreach ( $exclude_keys as $key ) {
				unset( $data[$key] );
			}

			return $data;
		}
		
		return array();
	}
	
	/*
	public function query( $table_name ) {
		$seven_days_ago = strtotime("-7 days");
		
		$response = $this->dynamoDB->query(array(
			'TableName' => $this->prefix.$table_name,
			'HashKeyValue' => array( AmazonDynamoDB::TYPE_STRING => 'Amazon DynamoDB#DynamoDB Thread 2' )
			//'TableName' => $this->prefix.$table_name,
			//'HashKeyValue' => array( AmazonDynamoDB::TYPE_NUMBER => '1' ),
			// optional parameters
			// 'AttributesToGet' => array( 'Subject', 'ReplyDateTime', 'PostedBy' ),
		    //'ConsistentRead' => true,
		    //'RangeKeyCondition' => array(
		    //    'ComparisonOperator' => AmazonDynamoDB::CONDITION_LESS_THAN_OR_EQUAL,
		    //    'AttributeValueList' => array(
		    //        array( AmazonDynamoDB::TYPE_NUMBER => $seven_days_ago )
		    //    )
		    //)
		));
		
		$error = $this->response( $response, true );
		
		if ( $error ) {
			return null;
		}
		
		return $this->objectWithXML( $response->body );
	}*/
	
	public function scan( $table_name, $non_prefix = false ) {
		$response = $this->dynamoDB->scan(array(
			'TableName' => $this->_getTableName( $table_name, $non_prefix )
		));
		
		$error = $this->response( $response );
		
		if ( $error ) {
			return null;
		}
		
		return $this->objectWithXML( $response->body );
	}
}

if ( ! function_exists('showArray')) {

function showArray() 
{
	$object = func_get_args();
	if ( count( $object ) == 1 ) {
		$object = $object[0];
	}
	
	if ( $object == "-" ) {
		echo str_repeat( "-=", 10 );
	}
	else {
		ob_start(); 
		print_r( $object ); 
		$print = ob_get_clean();
		$print = htmlspecialchars( $print );
	  
		echo sprintf( "<pre>%s</pre>", $print );
	}
}

}

if ( ! function_exists('object2Array')) {

function object2Array($d)
{
	if (is_object($d)) {
		$d = get_object_vars($d);
	}

	if (is_array($d)) {
		return array_map(__FUNCTION__, $d);
	}
	else {
		return $d;
	}
}

}

if ( ! function_exists('array2Object')) {

function array2Object($d)
{
	if (is_array($d)) {
		return (object) array_map(__FUNCTION__, $d);
	}
	else {
		return $d;
	}
}

}

?>
